package com.xhj.mybatis.transaction.jdbc;

import com.xhj.mybatis.session.TransactionIsolationLevel;
import com.xhj.mybatis.transaction.Transaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

/**
 * JdbcTransaction
 *
 * @author XJks
 * @description 基于 JDBC 的事务管理器，直接利用 JDBC 的 commit、rollback。依赖于数据源获得的连接来管理事务范围。
 */
public class JdbcTransaction implements Transaction {

    private Logger log = LoggerFactory.getLogger(JdbcTransaction.class);

    /**
     * 数据库连接
     */
    protected Connection connection;

    /**
     * 数据源
     */
    protected DataSource dataSource;

    /**
     * 事务隔离级别
     */
    protected TransactionIsolationLevel level = TransactionIsolationLevel.NONE;

    /**
     * 是否自动提交
     */
    protected boolean autoCommit;

    public JdbcTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
        this.dataSource = dataSource;
        this.level = level;
        this.autoCommit = autoCommit;
    }

    public JdbcTransaction(Connection connection) {
        this.connection = connection;
    }

    @Override
    public Connection getConnection() throws SQLException {
        if (connection == null) {
            openConnection();
        }
        return connection;
    }

    @Override
    public void commit() throws SQLException {
        if (connection != null && !connection.getAutoCommit()) {
            if (log.isDebugEnabled()) {
                log.debug("Committing JDBC Connection [" + connection + "]");
            }
            connection.commit();
        }
    }

    @Override
    public void rollback() throws SQLException {
        if (connection != null && !connection.getAutoCommit()) {
            if (log.isDebugEnabled()) {
                log.debug("Rolling back JDBC Connection [" + connection + "]");
            }
            connection.rollback();
        }
    }

    @Override
    public void close() throws SQLException {
        if (connection != null) {
            resetAutoCommit();
            if (log.isDebugEnabled()) {
                log.debug("Closing JDBC Connection [" + connection + "]");
            }
            connection.close();
        }
    }

    /**
     * 设置数据库连接的自动提交（autoCommit）属性为指定值。
     * 如果当前连接的 autoCommit 状态与期望值不同，则进行设置。
     * 如果设置过程中出现异常，则抛出自定义的 TransactionException。
     *
     * @param desiredAutoCommit 期望设置的 autoCommit 值（true 表示自动提交，false 表示手动提交）
     */
    protected void setDesiredAutoCommit(boolean desiredAutoCommit) {
        try {
            // 判断当前连接的 autoCommit 状态是否与期望值不同
            if (connection.getAutoCommit() != desiredAutoCommit) {
                // 如果日志级别为 debug，则输出设置 autoCommit 的日志
                if (log.isDebugEnabled()) {
                    log.debug("Setting autocommit to " + desiredAutoCommit + " on JDBC Connection [" + connection + "]");
                }
                // 设置连接的 autoCommit 属性为期望值
                connection.setAutoCommit(desiredAutoCommit);
            }
        } catch (SQLException e) {
            // TODO:源码相关说明:只有极少数实现不规范的数据库驱动才会在这里抛异常，这里无法做太多处理，只能抛出异常
            throw new RuntimeException("Error configuring AutoCommit.  "
                    + "Your driver may not support getAutoCommit() or setAutoCommit(). "
                    + "Requested setting: " + desiredAutoCommit + ".  Cause: " + e, e);
        }
    }

    /**
     * 重置连接的自动提交（autoCommit）属性为 true。
     * 这样做是为了确保在关闭连接前，数据库不会因为未提交的事务而报错。
     * 某些数据库（如 Sybase）如果没有提交/回滚就关闭连接会抛异常。
     */
    protected void resetAutoCommit() {
        try {
            // 如果当前连接不是自动提交模式
            if (!connection.getAutoCommit()) {
                if (log.isDebugEnabled()) {
                    log.debug("Resetting autocommit to true on JDBC Connection [" + connection + "]");
                }
                /**
                 * TODO:
                 *  源码相关说明:
                 *      1.MyBatis 在只执行 select 时不会自动 commit/rollback，但有些数据库会在 select 时开启事务，要求关闭前必须 commit/rollback。
                 *      2.这里通过设置 autoCommit 为 true 作为解决方案。
                 */
                connection.setAutoCommit(true);
            }
        } catch (SQLException e) {
            // 如果设置 autoCommit 过程中出现异常
            if (log.isDebugEnabled()) {
                log.debug("Error resetting autocommit to true "
                        + "before closing the connection.  Cause: " + e);
            }
            // 这里没有抛出异常，只是记录日志，避免影响后续关闭连接的操作
        }
    }

    /**
     * TODO:
     *  打开数据库连接，并根据配置设置事务隔离级别和自动提交属性。
     *      1.通过 dataSource 获取一个新的数据库连接，并赋值给 connection。
     *      2.如果指定了事务隔离级别，则设置当前连接的事务隔离级别。
     *      3.设置连接的自动提交（autoCommit）属性为期望值。
     *
     * @throws SQLException 如果获取连接或设置属性时发生 SQL 异常
     */
    protected void openConnection() throws SQLException {
        // 如果日志级别为 debug，则输出日志，方便排查问题
        if (log.isDebugEnabled()) {
            log.debug("Opening JDBC Connection");
        }
        // 从数据源获取一个新的数据库连接
        connection = dataSource.getConnection();
        // 如果指定了事务隔离级别，则设置连接的事务隔离级别
        if (level != null) {
            connection.setTransactionIsolation(level.getLevel());
        }
        // 设置连接的自动提交属性
        setDesiredAutoCommit(autoCommit);
    }
}
